/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.extend.util;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.star.framework.extend.annotation.DynamicTableTemplate;
import cn.star.framework.extend.core.Column;
import cn.star.framework.extend.core.ForeignKey;
import cn.star.framework.extend.core.Index;
import cn.star.framework.extend.core.PrimaryKey;
import cn.star.framework.extend.core.Table;
import cn.star.framework.extend.core.Unique;
import cn.star.framework.sql.type.JdbcMappingType;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.JDBCType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.persistence.AttributeConverter;
import javax.persistence.Convert;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.Comment;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

/**
 * 解析一个实体类型转换为{@link Table}<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Slf4j
public class EntityClassParse {
  public static Table parse(Class<?> type) {
    return parse(null, type);
  }

  public static Table parse(String tableName, Class<?> type) {
    Table table = new Table();

    // 设置表名
    DynamicTableTemplate dynamicTableTemplate = type.getAnnotation(DynamicTableTemplate.class);
    if (dynamicTableTemplate != null) {
      table.setCatalog(dynamicTableTemplate.catalog());
      table.setSchema(dynamicTableTemplate.schema());
      table.setName(dynamicTableTemplate.name());
      table.setComment(dynamicTableTemplate.comment());
    }
    if (StrUtil.isNotBlank(tableName)) {
      table.setName(tableName);
    }

    Field[] fields = ClassUtil.getDeclaredFields(type);
    if (ArrayUtil.isEmpty(fields)) {
      throw new RuntimeException(StrUtil.format("在{}中没有获取到字段列表", type.getName()));
    }

    // 设置表列
    List<Column> columns = new ArrayList<>(fields.length);
    for (Field field : fields) {
      columns.add(parse(field));
    }
    table.setColumns(columns);

    // 设置主键
    table.setPrimaryKey(getTablePrimaryKey(columns));

    // 设置外键约束
    table.setForeignKeys(getTableForeignKeys(columns));

    // 设置索引
    table.setIndexes(getTableIndexes(type, columns));

    // 设置唯一约束
    table.setUniques(getTableUniques(type, columns, table.getIndexes()));

    return table;
  }

  private static PrimaryKey getTablePrimaryKey(final List<Column> columns) {
    List<Column> list = columns.stream().filter(Column::isPrimary).collect(Collectors.toList());
    if (CollectionUtil.isNotEmpty(list)) {
      return new PrimaryKey("primary key", list);
    }

    return null;
  }

  private static List<ForeignKey> getTableForeignKeys(List<Column> columns) {
    List<ForeignKey> list = new ArrayList<>();
    log.warn("Not support set foreign keys");

    return list;
  }

  private static Column getTableColumn(String name, List<Column> columns) {
    Column column =
        columns.stream().filter(i -> name.equalsIgnoreCase(i.getName())).findFirst().orElse(null);
    if (column == null) {
      String str =
          StrUtil.indexOf(name, '_') > 0
              ? StrUtil.toCamelCase(name)
              : StrUtil.toUnderlineCase(name);

      column =
          columns.stream().filter(i -> str.equalsIgnoreCase(i.getName())).findFirst().orElse(null);
    }

    return column;
  }

  private static List<Index> getTableIndexes(Class<?> type, List<Column> columns) {
    List<Index> list = new ArrayList<>();
    DynamicTableTemplate dynamicTableTemplate = type.getAnnotation(DynamicTableTemplate.class);
    if (dynamicTableTemplate != null && ArrayUtil.isNotEmpty(dynamicTableTemplate.indexes())) {
      javax.persistence.Index[] indices = dynamicTableTemplate.indexes();
      for (javax.persistence.Index index : indices) {
        String name = index.name();
        String columnList = index.columnList();
        List<Column> indexColumns = new ArrayList<>();
        if (StrUtil.isNotBlank(columnList)) {
          String[] names = columnList.replaceAll(" ", "").split(",");
          for (String str : names) {
            Column column = getTableColumn(str, columns);
            if (column != null) {
              indexColumns.add(column);
            }
          }
        }
        if (!indexColumns.isEmpty()) {
          Index item = new Index(name, indexColumns);
          item.setUnique(index.unique());
          list.add(item);
        }
      }
    }

    return list;
  }

  private static List<Unique> getTableUniques(
      Class<?> type, List<Column> columns, List<Index> indexes) {
    List<Unique> list = new ArrayList<>();
    DynamicTableTemplate dynamicTableTemplate = type.getAnnotation(DynamicTableTemplate.class);
    if (dynamicTableTemplate != null && ArrayUtil.isNotEmpty(dynamicTableTemplate.uniques())) {
      UniqueConstraint[] uniques = dynamicTableTemplate.uniques();
      for (UniqueConstraint unique : uniques) {
        String name = unique.name();
        String[] columnNames = unique.columnNames();
        List<Column> uniqueColumns = new ArrayList<>();
        if (ArrayUtil.isNotEmpty(columnNames)) {
          for (String columnName : columnNames) {
            Column column = getTableColumn(columnName, columns);
            uniqueColumns.add(column);
          }
        }
        if (!uniqueColumns.isEmpty()) {
          list.add(new Unique(name, uniqueColumns));
        }
      }
    }

    for (Index index : indexes) {
      if (index.isUnique()) {
        list.add(new Unique(index.getName(), index.getColumns()));
      }
    }

    columns.stream()
        .filter(Column::isUnique)
        .collect(Collectors.toList())
        .forEach(i -> list.add(new Unique(i.getName(), Collections.singletonList(i))));

    return list;
  }

  private static Column parse(Field field) {
    Column column = new Column();

    column.setName(getColumnName(field));
    column.setComment(getColumnComment(field));
    column.setLength(getColumnLength(field));
    column.setPrecision(getColumnPrecision(field));
    column.setScale(getColumnScale(field));
    column.setNullable(getColumnNullable(field));
    column.setUnique(getColumnUnique(field));
    column.setPrimary(getColumnPrimary(field));
    column.setDefaultValue(getColumnDefaultValue(field));
    column.setJdbcType(getColumnJdbcType(field));
    column.setOrder(getColumnOrder(field));
    column.setPersistence(getColumnPersistence(field));
    column.setField(field);

    return column;
  }

  private static String getColumnName(Field field) {
    String name = field.getName();
    javax.persistence.Column column = field.getAnnotation(javax.persistence.Column.class);
    if (column != null && StrUtil.isNotBlank(column.name())) {
      name = column.name();
    }

    return name;
  }

  private static String getColumnComment(Field field) {
    Comment comment = field.getAnnotation(Comment.class);

    return comment != null ? comment.value() : null;
  }

  private static int getColumnLength(Field field) {
    javax.persistence.Column column = field.getAnnotation(javax.persistence.Column.class);

    return column != null ? column.length() : 255;
  }

  private static int getColumnPrecision(Field field) {
    javax.persistence.Column column = field.getAnnotation(javax.persistence.Column.class);

    return column != null ? column.precision() : 0;
  }

  private static int getColumnScale(Field field) {
    javax.persistence.Column column = field.getAnnotation(javax.persistence.Column.class);

    return column != null ? column.scale() : 0;
  }

  private static boolean getColumnNullable(Field field) {
    javax.persistence.Column column = field.getAnnotation(javax.persistence.Column.class);

    return column == null || column.nullable();
  }

  private static boolean getColumnUnique(Field field) {
    javax.persistence.Column column = field.getAnnotation(javax.persistence.Column.class);

    return column != null && column.unique();
  }

  private static boolean getColumnPrimary(Field field) {
    return field.getAnnotation(Id.class) != null;
  }

  private static String getColumnDefaultValue(Field field) {
    ColumnDefault cd = field.getAnnotation(ColumnDefault.class);

    return cd != null ? cd.value() : null;
  }

  /**
   * 获取泛型上面的类型<br>
   *
   * <pre>
   *   class ? implements AttributeConverter&lt;?, <span style='color: red'>?</span>&gt; {
   *     ...
   *   }
   * </pre>
   *
   * @param field 字段
   * @return 字段类型
   */
  private static Class<?> getColumnClassType(Field field) {
    try {
      Convert convert = field.getAnnotation(Convert.class);
      if (convert != null) {
        Class<?> clazz = convert.converter();
        if (clazz != null) {
          Type[] interfaces = clazz.getGenericInterfaces();
          if (ArrayUtil.isNotEmpty(interfaces)) {
            for (Type type : interfaces) {
              ParameterizedTypeImpl parameterizedType = (ParameterizedTypeImpl) type;
              Class<?> rawType = parameterizedType.getRawType();
              if (rawType != null && rawType.isAssignableFrom(AttributeConverter.class)) {
                Type[] rawTypeArgumentTypes = parameterizedType.getActualTypeArguments();
                if (rawTypeArgumentTypes != null && rawTypeArgumentTypes.length == 2) {
                  return (Class<?>) rawTypeArgumentTypes[1];
                }
              }
            }
          }
        }
      }
    } catch (Exception e) {
      log.error("get raw type error", e);
    }
    return field.getType();
  }

  private static JDBCType getColumnJdbcType(Field field) {
    Class<?> type = getColumnClassType(field);
    // 大文本
    Lob lob = field.getAnnotation(Lob.class);
    if (lob != null) {
      return String.class.isAssignableFrom(type) ? JDBCType.CLOB : JDBCType.BLOB;
    }
    // 枚举
    Enumerated enumerated = field.getAnnotation(Enumerated.class);
    if (enumerated != null) {
      return enumerated.value() == EnumType.STRING ? JDBCType.VARCHAR : JDBCType.INTEGER;
    }
    // 基本类型
    JDBCType jdbcType = JdbcMappingType.getJdbcType(type);
    if (jdbcType == null) {
      log.warn(
          "未能找到{}对应的{}类型，请检查{}配置，使用默认类型：{}",
          type.getName(),
          JDBCType.class.getSimpleName(),
          JdbcMappingType.class.getName(),
          JDBCType.VARCHAR);

      return JDBCType.VARCHAR;
    }

    return jdbcType;
  }

  private static int getColumnOrder(Field field) {
    // ApiModelProperty#position
    return 1;
  }

  private static boolean getColumnPersistence(Field field) {
    return field.getAnnotation(Transient.class) == null;
  }
}
